Skip to content

Conversation

@CDRussell
Copy link
Member

@CDRussell CDRussell commented Dec 17, 2025

Task/Issue URL: https://app.asana.com/1/137249556945/project/488551667048375/task/1212450128487247?focus=true

Description

Enables duck AI chat syncing for internal builds.

For testing, will require 2 or more devices/emulators, and each will need to be configured to hit duck chat internal config, details below.

Setting up for testing (do on each of the test devices)

  • Fresh install internal build type from this branch
  • Type ai.duckduckgo.com into the omnibar, and authorise yourself by signing in
  • When prompted, choose the blue button PRODUCTION - setup and go to duck.ai
  • Tap Agree and Continue when duck ai chat launches
  • You should see a INTERNAL badge
  • 🔁 Repeat for your other test device

Steps to test this PR

Logcat filter: message~:"DuckChat-Sync|getAIChatNativeConfigValues"

Syncing some chats between devices

Pre-requisite - set up sync between the two test devices

  • On Device A, set up sync Settings -> Sync & Backup -> Sync and Back Up This Device to completion
  • On Device A still, choose Sync With Another Device and choose Copy Text Code
  • On Device B now, go to Settings -> Sync & Backup and choose Sync With Another Device. Tap Manually Enter Code and Paste Code.

This should set up Device A and Device B to sync with each other.

Test steps
  • On Device A, open duck ai chat and start a chat
  • On Device B, open duck ai chat and wait a few seconds; verify the chat syncs over
  • On Device B, start a new chat
  • On Device A, open duck ai chat and wait a few seconds; verify the chat syncs over so you now have 2 chats on each

Native fire button

  • On Device A, visit native app Settings -> Data Clearing and enable Clear Duck.ai Chats
  • On same device, use the native fire button to clear data and let it restart. Verify duck ai chats are gone.
  • On Device B, open duck ai chat and verify the chats are deleted (might take 10-15s)

Validate feature flag gates feature

  • Disable aiChatSync feature flag on Device A. Kill app and re-open.
  • On same device, open duck ai chat, and verify in the logs that "supportsAIChatSync":false
  • On same device, create a new chat.
  • Open duck ai chat on Device B and verify the chat does NOT appear
  • Re-enabled aiChatSync. Kill app and re-open.

#### Testing when sync is disabled

  • On any device, disable sync by Settings -> Sync & Backup -> Turn Off Sync & Backup...
  • On same device, open duck ai chat
  • Open duck ai settings using overflow (top-left) -> Settings
  • Verify you see a button to Set Up Now. Tap it, verify you are taken to sync setup. Hit back button and verify you still see the Set Up Now button.
  • Tap it again, and this time set up sync (can just use Sync and Back Up This Device).
  • Once sync is set up, tap back to return to duck ai; verify Set Up Now button disappears (might not be immediate)

Note

Enables internal Duck AI chat syncing end-to-end across app, JS, and Sync layers.

  • Adds supportsAIChatSync to getAIChatNativeConfigValues
  • New JS handlers for AI Chat sync:
    • encryptWithSyncMasterKey / decryptWithSyncMasterKey (base64url utils)
    • setAIChatHistoryEnabled (persisted flag)
    • sendToSyncSettings/sendToSetupSync
    • getScopedSyncAuthToken (rescope) and getSyncStatus
  • Data layer: repository/datastore support for app background timestamp and AI chat history flag
  • Deletion flow: DuckAiChatDeletionListenerImpl stores background timestamp; DuckChatSyncDataManager now gates deletions on chat history enabled
  • Sync service/API: add token/rescope, switch dev URL to staging, delete AI chats endpoint covered; new handlers/tests
  • Build: add ContentScope Scripts + JS Messaging deps; extensive unit tests added

Written by Cursor Bugbot for commit 89e6b67. This will update automatically on new commits. Configure here.

Copy link
Member Author

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more

This stack of pull requests is managed by Graphite. Learn more about stacking.

@CDRussell CDRussell force-pushed the feature/craig/sync_duck_ai_chat_support branch 3 times, most recently from 2f70f7b to 9cc09c0 Compare December 17, 2025 17:02
@CDRussell CDRussell force-pushed the feature/craig/update_sync_crypto_apis_byte_array branch from d36ea31 to 32ff896 Compare December 17, 2025 17:02
@CDRussell CDRussell force-pushed the feature/craig/sync_duck_ai_chat_support branch from 9cc09c0 to 827e026 Compare December 17, 2025 17:07
@CDRussell CDRussell force-pushed the feature/craig/update_sync_crypto_apis_byte_array branch 2 times, most recently from 0c92c2f to 76cd1e5 Compare December 17, 2025 17:07
@CDRussell CDRussell force-pushed the feature/craig/sync_duck_ai_chat_support branch 2 times, most recently from 861bb89 to 344aba4 Compare December 17, 2025 17:09
@CDRussell CDRussell force-pushed the feature/craig/update_sync_crypto_apis_byte_array branch 2 times, most recently from e268906 to 3d1db92 Compare December 17, 2025 17:27
@CDRussell CDRussell force-pushed the feature/craig/sync_duck_ai_chat_support branch 2 times, most recently from a0131a0 to 671c8c0 Compare December 17, 2025 17:45
@CDRussell CDRussell force-pushed the feature/craig/update_sync_crypto_apis_byte_array branch 2 times, most recently from b8f6d64 to c46ac61 Compare December 17, 2025 17:51
@CDRussell CDRussell force-pushed the feature/craig/sync_duck_ai_chat_support branch 2 times, most recently from 3fea8ee to 2f2edb4 Compare December 17, 2025 17:55
@CDRussell CDRussell force-pushed the feature/craig/update_sync_crypto_apis_byte_array branch from c46ac61 to b706df5 Compare December 17, 2025 17:55
@CDRussell CDRussell force-pushed the feature/craig/sync_duck_ai_chat_support branch from 2f2edb4 to 6300bf8 Compare December 17, 2025 17:56
@CDRussell CDRussell force-pushed the feature/craig/update_sync_crypto_apis_byte_array branch 2 times, most recently from b2e25e9 to 8fad7c6 Compare December 17, 2025 17:58
@CDRussell CDRussell force-pushed the feature/craig/sync_duck_ai_chat_support branch from 6300bf8 to 7c494c7 Compare December 17, 2025 17:58
@CDRussell CDRussell force-pushed the feature/craig/update_sync_crypto_apis_byte_array branch from 8fad7c6 to 68609b8 Compare December 18, 2025 11:07
@CDRussell CDRussell force-pushed the feature/craig/sync_duck_ai_chat_support branch from 7c494c7 to f32b677 Compare December 18, 2025 11:07
@CDRussell CDRussell force-pushed the feature/craig/update_sync_crypto_apis_byte_array branch from 68609b8 to fa156a9 Compare December 18, 2025 11:18
@CDRussell CDRussell force-pushed the feature/craig/sync_duck_ai_chat_support branch from f32b677 to dcde3d7 Compare December 18, 2025 11:18
@CDRussell CDRussell force-pushed the feature/craig/update_sync_crypto_apis_byte_array branch from 4f4a176 to 5eacdaa Compare December 19, 2025 10:51
@CDRussell CDRussell force-pushed the feature/craig/sync_duck_ai_chat_support branch 2 times, most recently from afa24c5 to 4dd2d33 Compare December 19, 2025 13:39
@CDRussell CDRussell force-pushed the feature/craig/update_sync_crypto_apis_byte_array branch 2 times, most recently from 05596f0 to 610d556 Compare December 19, 2025 13:41
@CDRussell CDRussell force-pushed the feature/craig/sync_duck_ai_chat_support branch 2 times, most recently from ef3085d to cbfdecb Compare December 19, 2025 17:34
@CDRussell CDRussell force-pushed the feature/craig/sync_duck_ai_chat_support branch 8 times, most recently from c95cc23 to dce0017 Compare January 7, 2026 16:34
@CDRussell CDRussell force-pushed the feature/craig/update_sync_crypto_apis_byte_array branch from 610d556 to 7e9d370 Compare January 7, 2026 16:34
@CDRussell CDRussell force-pushed the feature/craig/sync_duck_ai_chat_support branch 2 times, most recently from de0f738 to 80efa15 Compare January 7, 2026 17:13
@CDRussell CDRussell marked this pull request as ready for review January 7, 2026 17:13
@CDRussell CDRussell force-pushed the feature/craig/update_sync_crypto_apis_byte_array branch from 7e9d370 to fea3179 Compare January 7, 2026 17:23
@CDRussell CDRussell force-pushed the feature/craig/sync_duck_ai_chat_support branch from 80efa15 to 59d202c Compare January 7, 2026 17:23

fun sendError(error: String) {
val errorPayload = JSONObject().apply {
put("ok", false)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we use the same payload in several classes, can we extract the keys (or perhaps even better, a class we can serialize/deserialize)?

override fun onStop(owner: LifecycleOwner) {
appBackgroundedTimestamp = System.currentTimeMillis()
logcat { "DuckChat-Sync: App went to background, stored timestamp: $appBackgroundedTimestamp" }
val timestamp = System.currentTimeMillis()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use CurrentTimeProvider instead?

return null
}

return runBlocking(dispatchers.io()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know runBlocking was already here before, but do we need it? Can we rely on observables instead now that RC flags support it?

import javax.inject.Inject

@ContributesMultibinding(AppScope::class)
class DecryptWithSyncMasterKeyHandler @Inject constructor(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a lot of duplicated code in these handlers. Can we refactor them to avoid that?

@CDRussell CDRussell force-pushed the feature/craig/sync_duck_ai_chat_support branch from 59d202c to 5cb7a5c Compare January 9, 2026 10:15
@CDRussell CDRussell force-pushed the feature/craig/update_sync_crypto_apis_byte_array branch from fea3179 to 5ec43f6 Compare January 9, 2026 10:15

jsMessaging.onResponse(JsCallbackData(jsonPayload, featureName, jsMessage.method, jsMessage.id!!)).also {
logcat { "DuckChat-Sync: responded to ${jsMessage.method} with $jsonPayload" }
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing exception handling for JS response could cause crash

Medium Severity

The jsMessaging.onResponse() call at line 75 is not wrapped in runCatching, unlike all other handlers in this PR (DecryptWithSyncMasterKeyHandler, EncryptWithSyncMasterKeyHandler, SetUpSyncHandler, GetScopedSyncAuthTokenHandler) which consistently protect this call with exception handling. If onResponse() throws an exception, it would propagate uncaught and potentially crash the app, whereas the other handlers gracefully log the error and continue.

Fix in Cursor Fix in Web

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants